package ca

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gitee.com/extrame/fb-runner/util"
	"github.com/extrame/fabric-ca/cmd/fabric-ca-client/command"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

func CreateOrg(base string, org *util.Organization) error {
	return create(base, org)
}

//Create 通过ca服务器生成配置文件，需要在执行成功ca服务器之后执行
func create(base string, org *util.Organization) error {

	url := org.GetCaHostAndPortStr()

	logrus.Infoln("Enrolling the CA admin")

	home := filepath.Join(base, "organizations", org.GetParentDir(), org.GetHost())

	cert := filepath.Join(base, "organizations", "fabric-ca/"+org.GetKey()+"/tls-cert.pem")

	if !org.Ca.IsLocal() {
		fmt.Println("CA服务器运行于", org.Ca.IP(), "请拷贝"+cert+"到目标目录并重新执行")
	}

	<-waitExists(cert)

	logrus.Info(cert, " is created")

	err := os.MkdirAll(home, 0700)
	if err == nil {
		err = command.Enroll(home, map[string]interface{}{
			"url":           "https://admin:adminpw@" + url,
			"tls.certfiles": cert,
			"caname":        "ca-" + org.GetKey(),
		}, org.Ca.IPPort())
		if err != nil {
			return errors.Wrap(err, "in connect to ca server")
		}
	}

	var mspConfig = filepath.Join(home, "msp/config.yaml")

	util.MkdirFor(mspConfig)

	mark := strings.ReplaceAll(url, ":", "-")
	mark = strings.ReplaceAll(mark, ".", "-")

	ioutil.WriteFile(mspConfig, []byte(`NodeOUs:
  Enable: true
  ClientOUIdentifier:
    Certificate: cacerts/`+mark+`-ca-`+org.GetKey()+`.pem
    OrganizationalUnitIdentifier: client
  PeerOUIdentifier:
    Certificate: cacerts/`+mark+`-ca-`+org.GetKey()+`.pem
    OrganizationalUnitIdentifier: peer
  AdminOUIdentifier:
    Certificate: cacerts/`+mark+`-ca-`+org.GetKey()+`.pem
    OrganizationalUnitIdentifier: admin
  OrdererOUIdentifier:
    Certificate: cacerts/`+mark+`-ca-`+org.GetKey()+`.pem
    OrganizationalUnitIdentifier: orderer`), 0700)

	logrus.Infoln("Registering the " + org.GetName() + " admin")

	err = command.Register(home, map[string]interface{}{
		"tls.certfiles": cert,
		"caname":        "ca-" + org.GetKey(),
		"id.name":       org.GetKey() + "admin",
		"id.secret":     org.GetKey() + "adminpw",
		"id.type":       "admin",
	}, org.Ca.IPPort())

	if err != nil && !strings.Contains(err.Error(), "already registered") {
		return errors.Wrap(err, "in register")
	}

	var host string
	if host, err = createPeer(cert, home, url, org); err == nil {
		util.Copy(filepath.Join(home, org.GetPeersDir(), host+"/tls/tlscacerts/*"), filepath.Join(home, "/msp/tlscacerts/ca.crt"))
		util.Copy(filepath.Join(home, org.GetPeersDir(), host+"/tls/tlscacerts/*"), filepath.Join(home, "/tlsca/tlsca."+org.GetHost()+"-cert.pem"))
		util.Copy(filepath.Join(home, org.GetPeersDir(), host+"/msp/cacerts/*"), org.GetCaPath(base))
	} else if err != util.NoLocalHost {
		return errors.Wrap(err, "in createPeer")
	} else {
		return nil
	}

	if !org.IsOrderer {

		createUser(home, home, cert, url, org, &User{
			Name: "user1",
		})
	}

	logrus.Infoln("Generating the org admin msp")

	err = command.Enroll(home, map[string]interface{}{
		"url":           "https://" + org.GetKey() + "admin:" + org.GetKey() + "adminpw@" + url,
		"mspdir":        filepath.Join(home, "/users/Admin@"+org.GetHost()+"/msp"),
		"tls.certfiles": cert,
		"caname":        "ca-" + org.GetKey(),
	}, org.Ca.IPPort())

	if err == nil {
		util.Copy(filepath.Join(home, "/users/Admin@"+org.GetHost()+"/msp/signcerts/cert.pem"), filepath.Join(home, "/users/Admin@"+org.GetHost()+"/msp/signcerts/Admin@"+org.GetHost()+"-cert.pem"))
	} else {
		return err
	}

	util.Copy(filepath.Join(home, "/msp/config.yaml"), filepath.Join(home, "/users/Admin@"+org.GetHost()+"/msp/config.yaml"))

	return nil
}

func createPeer(cert, home, port string, org *util.Organization) (string, error) {
	p, err := org.GetLocalPeer()
	if err != nil {
		return "", err
	}
	peer := p.GetKey()
	logrus.Infoln("Registering", peer)

	err = command.Register(home, map[string]interface{}{
		"tls.certfiles": cert,
		"caname":        "ca-" + org.GetKey(),
		"id.name":       peer,
		"id.secret":     peer + "pw",
		"id.type":       "peer",
	}, org.Ca.IPPort())

	if err != nil && !strings.Contains(err.Error(), "already registered") {
		return "", err
	}

	logrus.Infoln("Generating the " + peer + " msp")

	err = command.Enroll(home, map[string]interface{}{
		"url":           "https://" + peer + ":" + peer + "pw@" + port,
		"mspdir":        filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/msp"),
		"csr.hosts":     []string{p.GetHost()},
		"tls.certfiles": cert,
		"caname":        "ca-" + org.GetKey(),
	}, org.Ca.IPPort())
	//--csr.hosts
	if err != nil {
		return "", err
	}

	util.Copy(filepath.Join(home, "/msp/config.yaml"), filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/msp/config.yaml"))

	logrus.Infoln("Generating the " + peer + "-tls certificates")

	// cmd = exec.Command("fabric-ca-client",
	// 	"enroll", "-u", "https://"+peer+":"+peer+"pw@"+port, "--caname", "ca-"+org.GetKey(),
	// 	"--tls.certfiles", cert,
	// 	"-M", , ,
	// 	"--home", home)

	// _, _, err = util.Run(cmd)

	// // "--enrollment.profile",
	// // "--csr.hosts", , "--csr.hosts", ,

	err = command.Enroll(home, map[string]interface{}{
		"url":                "https://" + peer + ":" + peer + "pw@" + port,
		"mspdir":             filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls"),
		"csr.hosts":          []string{p.GetHost(), "localhost"},
		"enrollment.profile": "tls",
		"tls.certfiles":      cert,
		"caname":             "ca-" + org.GetKey(),
	}, org.Ca.IPPort())

	if err == nil {
		util.Copy(filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls/tlscacerts/*"), filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls/ca.crt"))
		util.Copy(filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls/signcerts/*"), filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls/server.crt"))
		util.Copy(filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls/keystore/*"), filepath.Join(home, org.GetPeersDir(), p.GetHost()+"/tls/server.key"))
	}

	return p.GetHost(), err
}

func waitExists(name string) chan bool {
	c := make(chan bool)
	go func() {
		logrus.Infof("check %s exist or not", name)
		for {
			if _, err := os.Stat(name); err != nil {
				if os.IsNotExist(err) {
					time.Sleep(1 * time.Second)
					continue
				}
			}
			c <- true
			break
		}
	}()
	return c
}

type User struct {
	Name       string
	Pwd        string
	JustEnroll bool
}

func (u *User) GetPwd() string {
	if u.Pwd != "" {
		return u.Pwd
	}
	return u.Name + "pw"
}

func (u *User) GetTitle() string {
	if u.Name == "" {
		return ""
	}
	return strings.ToTitle(u.Name[0:1]) + u.Name[1:]
}

//Create 通过ca服务器生成配置文件，需要在执行成功ca服务器之后执行
func CreateUser(base string, clientBase string, org *util.Organization, u *User) error {

	url := org.GetCaHostAndPortStr()

	logrus.Infoln("Enrolling the CA admin")

	home := filepath.Join(base, "organizations", org.GetParentDir(), org.GetHost())

	clientHome := filepath.Join(clientBase, "organizations", org.GetParentDir(), org.GetHost())

	cert := filepath.Join(clientBase, "organizations", "fabric-ca/"+org.GetKey()+"/tls-cert.pem")

	if !org.Ca.IsLocal() {
		fmt.Println("CA服务器运行于", org.Ca.IP(), "请拷贝"+cert+"到目标目录并重新执行")
	}

	<-waitExists(cert)

	logrus.Info(cert, " is created")

	err := os.MkdirAll(clientHome, 0700)

	if err == nil {

		mark := strings.ReplaceAll(url, ":", "-")
		mark = strings.ReplaceAll(mark, ".", "-")

		logrus.Infoln("Generating the org " + u.Name + " msp")

		return createUser(home, clientHome, cert, url, org, u)
	}
	return err
}

func createUser(home, clientHome, cert, url string, org *util.Organization, u *User) (err error) {
	logrus.Infoln("Registering user")

	if !u.JustEnroll {
		err = command.Register(clientHome, map[string]interface{}{
			"tls.certfiles":  cert,
			"caname":         "ca-" + org.GetKey(),
			"id.name":        u.Name,
			"id.secret":      u.GetPwd(),
			"id.type":        "client",
			"id.affiliation": org.GetKey() + ".department1",
		}, org.Ca.IPPort())

		if err != nil && !strings.Contains(err.Error(), "already registered") {
			return err
		}
	}

	logrus.Infoln("Generating the user msp", u)

	err = command.Enroll(home, map[string]interface{}{
		"url":           "https://" + u.Name + ":" + u.GetPwd() + "@" + url,
		"mspdir":        filepath.Join(home, "/users/"+u.GetTitle()+"@"+org.GetHost()+"/msp"),
		"tls.certfiles": cert,
		"caname":        "ca-" + org.GetKey(),
	}, org.Ca.IPPort())

	if err == nil {
		util.Copy(filepath.Join(home, "/users/"+u.GetTitle()+"@"+org.GetHost()+"/msp/signcerts/cert.pem"),
			filepath.Join(home, "/users/"+u.GetTitle()+"@"+org.GetHost()+"/msp/signcerts/"+u.GetTitle()+"@"+org.GetHost()+"-cert.pem"))
	} else {
		return err
	}

	util.Copy(filepath.Join(clientHome, "/msp/config.yaml"), filepath.Join(home, "/users/"+u.GetTitle()+"@"+org.GetHost()+"/msp/config.yaml"))
	return err
}
